#include "socketstream.hpp"
#include <cstdio>
#include <memory>
#include "log.hpp"
#include "sockets_impl.hpp"

namespace vl
{

socket_buf::socket_buf(socket sock)
: sock(sock), flags(connecting)
{
	vl::init_sockets();
	set_no_blocking(sock);
}

socket_buf::socket_buf(char const* addr, int port)
: sock(), flags(error_occured)
{
	vl::init_sockets();

	internet_sockaddr server;
	
	std::auto_ptr<vl::host_entry> hp(vl::resolve_host( addr ));
	
	if(!hp.get())
		return;
	
	vl::create_addr(server, hp.get(), port);
	
	socket s;
	s = vl::socket_nonblock();
	if(!s.valid())
		return;
		
	if(!vl::connect(s, server))
		return;

	sock = s;
	replace_flags(connecting);
}

size_t socket_buf::read(byte* p, size_t size)
{
	process();
	
	if(all_flags(connected))
	{
		int r = ::recv(native_socket(sock), (char*)p, static_cast<int>(size), 0);
		if(r != sock_error_ret)
		{
			if(r == 0)
			{
				return device_buf::eof_value;
			}
			DLOG("Recieved " << r << " bytes on " << sock);
			return r;
		}
		else if(sock_error() != sockerr_would_block)
		{
			ELOG("Error reading " << native_socket(sock));
			set_flags(error_occured);
			return device_buf::eof_value;
		}
		
	}

	return 0;
}

size_t socket_buf::write(byte const* p, size_t size)
{
	process();
	
	if(all_flags(connected))
	{
		int sent = ::send(native_socket(sock), (char const*)p, static_cast<int>(size), 0);
		if(sent == sock_error_ret)
		{
			if(sock_error() != sockerr_would_block) // Error
			{
				ELOG("Error writing " << native_socket(sock));
				set_flags(error_occured);
				return 0;
			}
			return 0;
		}
		else if(sent > 0)
		{
			DLOG("Sent " << sent << " bytes on " << sock);
			return sent;
		}
	}
	
	return 0;
}

void socket_buf::process()
{
	if(all_flags(connecting) && no_flags(error_occured))
	{
		fd_set monitor;
		timeval tv;
		tv.tv_sec = 0;
		tv.tv_usec = 0;
		FD_ZERO(&monitor);
		FD_SET(native_socket(sock), &monitor);

#ifdef WINDOWS
		select(0, 0, &monitor, 0, &tv);
#else
		select(native_socket(sock)+1, 0, &monitor, 0, &tv);
#endif
		
		if(FD_ISSET(native_socket(sock), &monitor))
		{
			int status;
			socklen_t len = sizeof(status);
			getsockopt(native_socket(sock), SOL_SOCKET, SO_ERROR, (char *)&status, &len);
			if(status != 0) // Error
			{
				ELOG("Error connecting " << native_socket(sock));
				set_flags(error_occured);
				return;
			}
			
			change_flags(connected, connected | connecting);
		}
	}
}

bool socket_buf::wait_for_data(int microsec)
{
	process();
	
	if(no_flags(connected))
		return false;
		
	fd_set monitor;
	timeval tv;
	tv.tv_sec = microsec / 1000000;
	tv.tv_usec = microsec % 1000000;
	FD_ZERO(&monitor);
	FD_SET(native_socket(sock), &monitor);

#ifdef WINDOWS
	select(0, &monitor, 0, 0, &tv);
#else
	select(native_socket(sock)+1, &monitor, 0, 0, &tv);
#endif
	
	if(FD_ISSET(native_socket(sock), &monitor))
		return true;
		
	return false;
}

void socket_buf::do_close()
{
	if(any_flag(connected | connecting))
	{
		sock.close();
		reset_flag(connected | connecting | error_occured);
	}
}

socket_buf::~socket_buf()
{
	sock.close();
	deinit_sockets();
}

/////

socket_listener::socket_listener()
: sock(), blocking(0)
{
	init_sockets();
}

socket_listener::socket_listener(int port)
: sock(), blocking(0)
{
	init_sockets();
	listen(port);
}

socket_listener::~socket_listener()
{
	sock.close();
	deinit_sockets();
}

bool socket_listener::listen(int port)
{
	sock.close();
		
	socket s;
	s = socket_nonblock();
	if(!s.valid())
		return false;
		
	bind(s, port);
	
	vl::listen(s);
		
	sock = s;
	return true;
}

socket_buf* socket_listener::accept()
{
	if(!sock.valid())
		return 0;
		
	while(true)
	{
		internet_sockaddr addr;
		socket news = vl::accept(sock, addr);
		
		if(news.valid())
			return new socket_buf(news);
			
		if(sock_error() != sockerr_would_block) // Error
		{
			ELOG("Error accepting on " << native_socket(sock));
			sock.close();
			return 0;
		}
		
		if(blocking)
		{
			blocking(this);
			continue;
		}
		
		return 0;
	}
}

void socket_listener::close()
{
	if(sock.valid())
		sock.close();
	sock = socket();
}
	
/////

socketstream::socketstream(char const* addr, int port)
: iobase(new socket_buf(addr, port))
{
}

/*
socketstream::socketstream(char const* addr, int port)
: dev(0)
{
	TCP::init();

	sockaddr_in server;
	
	std::auto_ptr<TCP::Hostent> hp(TCP::resolveHost( addr ));
	
	if(!hp.get())
		return;
	
	DLOG("Resolved host");
	
	TCP::createAddr(server, hp.get(), port);
	
	Socket s;
	if((s = TCP::socketNonBlock()) < 0)
		return;
	
	DLOG("Created socket");
		
	if(!TCP::connect(s, server))
		return;
	
	DLOG("Initialized connection");
	
	dev = new socket_buf(s);
	
	DLOG("Device set");
}*/

}
